package vzaar

import scala.xml._ 
import scala.collection.immutable._

import java.io.File
import java.io.OutputStream

/**
 * Trait for monitoring upload progress. Clients should implement this 
 * interface to get progress or report progress. Progress reports are up to
 * the implementing transport but should be approximately every 1%. There 
 * should also be a guarantee that either error or complete is called at
 * the end of the process.
 * 
 * @author Marc G. Smith
 */
trait UploadProgressCallback {
	/**
	 * An error occurred during the upload progress.
	 *  
	 * @param fileName the name of the file being uploaded
	 * @param error the error cause
	 */
	def error(fileName:String, error:String);
 
	/**
	 * A progress update.
	 * 
	 * @param fileName the name of the file being uploaded
	 * @param sent the number of bytes sent so far
	 * @param length the total number of bytes to be uploaded
	 */
	def progress(fileName:String, sent:Long, length: Long);
 
	/**
	 * The upload procedure has completed if an error hasn't occured.
	 * 
	 * @param fileName the name of the file being uploaded
	 * @param videoId the video id of the uploaded video 
	 */
	def complete(fileName:String, videoId:String);
}

/**
 * Useful vzaar constants
 * 
 * @author Marc G. Smith
 */
object Vzaar {
	/**
	 * Small video size profile 
	 */
	val ProfileSmall = "1"
 
	/**
	 * Medium video size profile 
	 */
	val ProfileMedium = "2"
 
	/**
	 * Medium video size profile 
	 */
	val ProfileLarge = "3"
 
	/**
	 * High definition video size profile 
	 */
	val	ProfileHighDefinition = "4"
 
	/**
	 * Original video size profile 
	 */
	val ProfileOriginal = "5"

	/**
     * HTTP OK Status 
	 */
	private[vzaar] val HttpOk = 200
 
	/**
     * URL to vzaar live server   
	 */
	val UrlLive = "https://vzaar.com/"

	/**
     * URL to vzaar sandbox server  
	 */
	val UrlSandbox= "https://sandbox.vzaar.com/"
}

/**
 * vzaar API access. This is the main class for accessing vzaar through
 * their RESTful API.
 *
 * Some of the methods require authentication which is done using OAuth.
 * If you need to use these methods then ensure you create a Vzaar
 * object with your OAuth credentials. The OAuth token is typically
 * your username and the OAuth secret can be retrieved from your 
 * account page on <a target="_blank" href="http://vzaar.com">vzaar.com</a>.
 *
 * The default implementation uses the vzaar java API default transport.
 *
 * @param url The API URL such as http://sandbox.vzaar.com/api/
 * @param oauthToken Your vzaar login name
 * @param oauthSecret Your generated application token 
 * @author Marc G Smith
 */
class Vzaar(url:String, oauthToken:String, oauthSecret:String) {

	private val transport = VzaarTransport.create();
	transport.setUrl(url);
	transport.setOAuthTokens(oauthToken, oauthSecret);

	/**
	 * Should the upload method be accepting only trusted formats. If this is set
	 * to true then any formats that are not on the trusted list will throw
	 * an exception up front on the uploadVideo() method. Otherwise all
	 * accepted formats will be allowed.
	 */
	var trustedFormatsOnly = true;
	
	def this(oauthToken:String, oauthSecret:String) = this(Vzaar.UrlLive, oauthToken, oauthSecret)
	def this(url:String) = this(url, null, null)
	def this() = this(Vzaar.UrlLive)
 
	/**
	 * Set the output stream for debug output if required. 
	 * 
	 * @param out the output stream for debug.
	 */
	def setDebugStream(debugStream:OutputStream) = transport.setDebugStream(debugStream)
 
	/**
	 * Get a user's public details along with it's relevant metadata. 
	 * 
	 * Note: The user name must be used and not the email address.
	 * 
	 * @param username the vzaar login for that user.
	 * @return the user information
	 */
	def userDetails(username:String):User = { 
		val uri = username + ".xml"
		var response = transport.sendGetRequest(uri, null)
		
		if(response.statusCode != Vzaar.HttpOk) {
		  error(parseError(response))
		}
			
		User.parse(response)
	}
 
	/**
	 * Get the details and rights for an account type.
	 * 
	 * @param accountType the account type to fetch the details for
	 * @return the account type details 
	 */
	def accountType(accountType:String):AccountType = {
		var uri = "accounts/" + accountType + ".xml";
		var response = transport.sendGetRequest(uri, null)
		
		if(response.statusCode != Vzaar.HttpOk) {
		  error(parseError(response))
		}
  
		AccountType.parse(response);
	}
 

	/**
	 * Get a list of the user's active videos along with it's relevant 
	 * metadata.
	 * 
	 * @param request the video list request parameters.
	 * @return a list of videos for the request object 
	 */
	def videoList(request:VideoListRequest):List[Video] = {

		var username = request.username;
		if(username == null) {
			error("Required parameter 'username' is missing from request");
		}
		
		var uri = username + "/videos.xml";
		var response = transport.sendGetRequest(uri, request.asParameters());
		
		if(response.statusCode != Vzaar.HttpOk) {
		  error(parseError(response))
		}

		Video.parse(response); 
	}

	/**
	 * Get a video details including embed code.
	 * 
	 * @param request the video detail request parameters
	 * @return the video details 
	 */
	def videoDetails(request:VideoDetailsRequest):VideoDetails = { 
		var videoId = request.videoId;
		if(videoId == null) {
			error("Required video id is missing from request")
		}
		
		var uri = "videos/" + videoId + ".xml"

		var response = transport.sendGetRequest(uri, request.asParameters)
		if(response.statusCode != Vzaar.HttpOk) {
		  error(parseError(response))
		}
  
		VideoDetails.parse(response)
	}

	/**
	 * Test method for authentication.
	 * 
	 * @return the user name of the authenticated user
	 */
	def whoAmI():String = {
		var uri = "test/whoami"
		var response = transport.sendGetRequest(uri, null)
		
		if(response.statusCode != Vzaar.HttpOk) {
		  error(parseError(response))
		}
		
		var document = XML.load(response.response)
		return VzaarUtil.xmlAsString(document, "login");
	}
	
	/**
	 * Upload a video to vzaar. This is tripart request which fetches the 
	 * upload signature, uploads to S3 and then requests vzaar to process.
	 * 
	 * @param title the video title
	 * @param description the video description
	 * @param profile the size for the video to be encoded
	 * @param file the file to be uploaded
	 */
	def uploadVideo(
	  title:String, description:String, profile:String, fileName:String):String = {
		uploadVideo(title, description, profile, fileName, null);
	}
	
	/**
	 * Upload a video to vzaar. This is tri-part request which fetches the 
	 * upload signature, uploads to S3 and then requests vzaar to process.
	 * 
	 * This method is a convenience method to reduce the upload process to 
	 * a single call rather than having to make the three separate back end
	 * calls required to complete the transaction. 
	 * 
	 * The callback object is used to report progress of the upload.
	 * 
	 * @param title the video title
	 * @param description the video description
	 * @param profile the size for the video to be encoded
	 * @param file the file to be uploaded
	 * @param callback the progress callback object
	 * @return the video number for the video
	 */
	def uploadVideo(
		title:String, description:String, profile:String, fileName:String,
		callback:UploadProgressCallback):String = 
   {
	  var file = new java.io.File(fileName)
	  try {
			if(trustedFormatsOnly) {
				if(!VzaarUtil.isTrustedFormat(file.getName)) {
					error("Video is not trusted and tested format")
				}
			}
			else {
				if(!VzaarUtil.isAcceptedFormat(file.getName)) {
					error("Video is not of an accepted format")
				}
			}
				
			var signature = uploadSignature()
			upload(signature, fileName, callback)				
			var videoId = processVideo(signature.guid, title, description, profile)
			if(callback != null) callback.complete(fileName, videoId);
			videoId;
		}
		catch {
		  case e => {
			  if(callback != null) callback.error(fileName, e.getMessage)
			  error(e.getMessage);
		  }
		}
	}
	
	/**
	 * Edit a video title and description.
	 * 
	 * @param videoId the video number for the video
	 * @param title the video title
	 * @param description the video description
	 */
	def editVideo(videoId:String, title:String, description:String) = {
		var uri = "videos/" + videoId + ".xml";
		var response = transport.sendPostXmlRequest(uri, String.format(
		  VzaarUtil.EDIT_VIDEO, 
		  Utility.escape(title), 
		  Utility.escape(description)))
		
		if(response.statusCode / Vzaar.HttpOk != 1) {
			error(parseError(response));
		}
		else {
			XML.load(response.response)
		}
	}

	/**
	 * Delete a video from a users account.
	 * 
	 * @param videoId the video number for the video
	 */
	def deleteVideo(videoId:String) = {
		var uri = "videos/" + videoId + ".xml";
		var response = transport.sendPostXmlRequest(uri, VzaarUtil.DELETE_VIDEO);
		
		if(response.statusCode / Vzaar.HttpOk != 1) {
			error(parseError(response));
		}
		else {
			XML.load(response.response)
		}
	}

	/**
	 * Process the video after it has been uploaded.
	 * 
	 * @param guid the guid to operate on
	 * @param title the title for the video
	 * @param description  the description for the video
	 * @param profile the size for the video to be encoded in. If not 
	 * 	specified, this will use the vzaar default or the user default (if set)
	 * @return the video number for the video
	 */
	def processVideo(
		guid:String, title:String, description:String, profile:String):String = {
		var uri = "videos";
		var response = transport.sendPostXmlRequest(uri, String.format(
		  VzaarUtil.PROCESS_VIDEO,
		  guid,
		  Utility.escape(title), 
		  Utility.escape(description),
          profile))
		
		if(response.statusCode / Vzaar.HttpOk != 1) {
			error(parseError(response));
		}

		var document = XML.load(response.response)
		VzaarUtil.xmlAsString(document, "video")
	}

	/**
	 * Get a global uid for vzaar and signature to upload to S3.
	 * 
	 * @return the upload signature details.
	 */
	def uploadSignature() : UploadSignature = {
		var uri = "videos/signature"
		var response = transport.sendGetRequest(uri, null)
	
		if(response.statusCode != Vzaar.HttpOk) {
			error(parseError(response));
		}
		
		UploadSignature.parse(response);
	}

	/**
	 * Upload a file to S3. The upload signature must be fetched for each new
	 * video.
	 * 
	 * @param signature the signature returned from getUploadSignature()
	 * @param file the file to upload
	 * @param callback the progress callback or null if not required
	 */
	def upload(
		signature:UploadSignature, fileName:String, 
		callback:UploadProgressCallback) = {
		
	    var url = "https://" + signature.bucket + ".s3.amazonaws.com/"
		var parameters = Map(
			"bucket" -> signature.bucket,
			"key" -> signature.key,
			"AWSAccessKeyId" -> signature.accessKeyId,
			"acl" -> signature.acl,
			"policy" -> signature.policy,
			"signature" -> signature.signature,
			"success_action_status" -> "201")
		
		var file = new java.io.File(fileName) 
		var response = 
			transport.uploadToS3(url, parameters, file, callback);
		
		if(response.statusCode / Vzaar.HttpOk != 1) 
		{
			error(
				"Upload to Amazon S3 returned the following error: " +
				"'" + parseError(response) + "'");
		}
	}

	///////////////////////////////////////////////////////////////////////////
	// Private Methods ////////////////////////////////////////////////////////
	///////////////////////////////////////////////////////////////////////////

	/**
	 * Extract the error string from the response. It will first check for 
	 * an error tag if there is an xml reponse, otherwise it will return
	 * the HTTP error status line. 
	 */
	private def parseError(response: VzaarTransportResponse):String = {
		var errorMsg:String = null;
		try {
			var document = XML.load(response.response)
			errorMsg = VzaarUtil.xmlAsString(document, "error")

			if(errorMsg == null) {
				errorMsg = VzaarUtil.xmlAsString(document, "Message")
			}
		}
		catch {
		  case _ => print("")
		}
		
		if(errorMsg == null) errorMsg = response.statusLine
		errorMsg.trim;
	}
}

